﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Program.cs" company="NAnt2GoogleCodeWiki team">
//
// nant-cross-compile, an extension for NAnt for cross-compiling C/C++ applications.
// Copyright (C) 2012 nant-cross-compile team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/. 
// </copyright>
// <history>
//   <historyitem date="2012-10-27" change="Created file"/>
// </history>
// --------------------------------------------------------------------------------------------------------------------
namespace NAnt2GoogleCodeWiki
{
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Text;
  using System.Xml;
  using NAntReflectorLib;
  using NAntReflectorLib.NAntTypes;
  using WikiDocLib;
  using System.Reflection;
using System.Threading;

  /// <summary>
  /// Entry point of the application.
  /// </summary>
  public class Program
  {
    /// <summary>
    /// The XML doc
    /// </summary>
    private static XmlDocument xmlDoc = new XmlDocument();

    /// <summary>
    /// The documented namespaces.
    /// </summary>
    private static List<string> documentedNameSpaces = new List<string>()
    {
      "NAnt"
    };

    /// <summary>
    /// Entry point of the application.
    /// </summary>
    /// <param name="args">The args.</param>
    public static void Main(string[] args)
    {
      string appDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
      string dllDirectory = Path.Combine(appDirectory, @"..\..\..\_external\nant-0.92\bin");
      string outDirectory = Path.Combine(appDirectory, "Output");

      List<Thread> documenterThreads = new List<Thread>();
      List<NAntEntity> repository = new List<NAntEntity>();

      // Load all required assemblies
      string requiredAssembly = Path.Combine(dllDirectory, @"lib\common\neutral\NUnitCore.dll");
      Assembly.Load(AssemblyName.GetAssemblyName(requiredAssembly));
      requiredAssembly = Path.Combine(dllDirectory, @"lib\common\neutral\NDoc.Core.dll");
      Assembly.Load(AssemblyName.GetAssemblyName(requiredAssembly));

      foreach (string dllpath in Directory.GetFiles(dllDirectory, "*.dll", SearchOption.TopDirectoryOnly))
      {
        NAntReflector reflector = new NAntReflector(dllpath);
        repository.AddRange(reflector.Tasks);
        repository.AddRange(reflector.Types);
        DocumentNantTypes(Path.Combine(outDirectory, "Tasks"), "Task", reflector.Tasks, repository);
        DocumentNantTypes(Path.Combine(outDirectory, "Types"), "Type", reflector.Types, repository);
      }
    }

    /// <summary>
    /// Documents the NANT types.
    /// </summary>
    /// <param name="docPath">The doc path.</param>
    /// <param name="entityType">Type of the entity.</param>
    /// <param name="entities">The entities.</param>
    /// <param name="repository">The repository.</param>
    private static void DocumentNantTypes(string docPath, string entityType, List<NAntEntity> entities, List<NAntEntity> repository)
    {
      if (Directory.Exists(docPath) == false)
      {
        Directory.CreateDirectory(docPath);
      }

      foreach (NAntEntity info in entities)
      {
        // If the entity is abstract or has no NAnt name, it cannot be acccessed directly in a NAnt build file
        // skip it
        if ((info.IsAbstract == true) || (info.Name == null))
        {
          continue;
        }

        GoogleCodeWiki wiki = new GoogleCodeWiki();

        // Document header
        wiki.AddSummary("<" + info.Name + "> " + entityType);
        wiki.AddHeading("`<" + info.Name + ">`", 1);
        wiki.AddText("<wiki:toc/>");
        
        if ((info.Summary != null) && (info.Summary.Count > 0))
        {
          wiki.AddParagraph();
          wiki.AddHeading("Summary", 2);
          CreateWikiSection(info.Summary, wiki, repository, info.Type.FullName);
        }

        if ((info.Remarks != null) && (info.Remarks.Count > 0))
        {
          wiki.AddParagraph();
          wiki.AddHeading("Remarks", 2);
          CreateWikiSection(info.Remarks, wiki, repository, info.Type.FullName);
        }

        if ((info.Parameters != null) && (info.Parameters.Count > 0))
        {
          // Sort parameters in ascending order
          info.Parameters.Sort(delegate(NAntEntityParameter p1, NAntEntityParameter p2) { return p1.NAntName.CompareTo(p2.NAntName); });

          // Write wiki header of section
          wiki.AddParagraph();
          wiki.AddHeading("Parameters", 2);
          wiki.AddTableHeader("Attribute", "Type", "Description", "Required");

          // Get parameters
          foreach (NAntEntityParameter parameter in info.Parameters)
          {
            StringBuilder summary = new StringBuilder();
            GoogleCodeWiki tempWiki = new GoogleCodeWiki();
            CreateWikiSection(parameter.Summary, tempWiki, repository, info.Type.FullName);
            wiki.AddTableRow(parameter.NAntName, parameter.ParameterType.Name, tempWiki.ToString(), parameter.Required.ToString());
          }
        }

        if ((info.NestedElements != null) && (info.NestedElements.Count > 0))        
        {
          // Sort parameters in ascending order
          info.NestedElements.Sort(delegate(NAntEntityParameter p1, NAntEntityParameter p2) { return p1.NAntName.CompareTo(p2.NAntName); });

          // Write wiki header of section
          wiki.AddParagraph();
          wiki.AddHeading("Nested Elements", 2);

          // Get nested elements
          foreach (NAntEntityParameter nestedElement in info.NestedElements)
          {
            wiki.AddHeading("`<" + nestedElement.NAntName + ">`", 3);
            CreateWikiSection(nestedElement.Summary, wiki, repository, info.Type.FullName);
            wiki.AddParagraph();
            CreateWikiSection(nestedElement.Remarks, wiki, repository, info.Type.FullName);
            wiki.AddParagraph();
          }
        }

        wiki.WriteToFile(Path.Combine(docPath, info.Type.Name + ".wiki"));
      }
    }

    /// <summary>
    /// Creates the wiki section.
    /// </summary>
    /// <param name="elements">The elements.</param>
    /// <param name="wiki">The wiki.</param>
    /// <param name="repository">The repository.</param>
    /// <param name="elementFullName">Full name of the element.</param>
    private static void CreateWikiSection(List<XmlDocElement> elements, GoogleCodeWiki wiki, List<NAntEntity> repository, string elementFullName)
    {
      if (elements == null)
      {
        return;
      }

      foreach (XmlDocElement xmlElement in elements)
      {
        switch (xmlElement.Type)
        {
          case XmlDocElementType.List:
          case XmlDocElementType.ListItem:
          case XmlDocElementType.Remarks:
          case XmlDocElementType.See:
          case XmlDocElementType.Summary:
            // Ignore it
            break;
          case XmlDocElementType.Paragraph:
            wiki.AddParagraph();
            break;
          case XmlDocElementType.Text:
            wiki.AddText(RemoveXmlStyle(xmlElement.Value));
            break;
          case XmlDocElementType.ListItemDescription:
            wiki.AddParagraph();
            wiki.AddBulletListItem(string.Empty);
            break;
          case XmlDocElementType.Reference:
            wiki.AddText(GetReferenceText(xmlElement.Value, repository, elementFullName));
            break;
          case XmlDocElementType.LanguageWord:
            wiki.AddText(" *" + xmlElement.Value + "* ");
            break;
          default:
            wiki.AddText(xmlElement.Value);
            break;
        }        
      }
    }

    /// <summary>
    /// Gets the reference text.
    /// </summary>
    /// <param name="referenceXmlText">The reference XML text.</param>
    /// <param name="repository">The reflector.</param>
    /// <param name="elementFullName">Full name of the element.</param>
    /// <returns>
    /// The reference text.
    /// </returns>
    private static string GetReferenceText(string referenceXmlText, List<NAntEntity> repository, string elementFullName)
    {
      const string PropertyIndicator = "P:";
      const string TypeIndicator = "T:";
      referenceXmlText = RemoveXmlStyle(referenceXmlText);

      if (referenceXmlText.StartsWith(PropertyIndicator) == true)
      {
        referenceXmlText = referenceXmlText.Substring(PropertyIndicator.Length);
        int lastDotPosition = referenceXmlText.LastIndexOf(".");
        string typeName = referenceXmlText.Substring(0, lastDotPosition);
        string propertyName = referenceXmlText.Substring(lastDotPosition + 1);
        
        // Check all types if there is a corresponding entry
        foreach (NAntEntity typeInfo in repository)
        {
          if (((typeInfo.Type.FullName == typeName) || (typeInfo.Type.FullName == elementFullName)) && (typeInfo.IsAbstract == false))
          {
            foreach (var a in typeInfo.NestedElements)
            {
              if (a.Name == propertyName)
              {
                string xmlTag = "<" + a.NAntName + ">";
                return " [" + typeInfo.Type.Name + "#" + xmlTag + " " + xmlTag + "] ";
              }
            }

            foreach (var a in typeInfo.Parameters)
            {
              if (a.Name == propertyName)
              {
                return " [" + typeInfo.Type.Name + "#Parameters" + " " + a.NAntName + "] ";
              }
            }
          }
        }
      }
      else if (referenceXmlText.StartsWith(TypeIndicator) == true)
      {
        referenceXmlText = referenceXmlText.Substring(TypeIndicator.Length);
        string typeName = referenceXmlText;
        foreach (NAntEntity typeInfo in repository)
        {
          if (typeInfo.Type.FullName == typeName)
          {
            foreach (string docNamespace in documentedNameSpaces)
            {
              if (typeName.StartsWith(docNamespace))
              {
                return " [" + typeInfo.Type.Name + " <" + typeInfo.Name + ">] ";
              }
            }

            return " `<" + typeInfo.Name + ">` ";
          }
        }
      }
      
      return " " + referenceXmlText + " ";
    }

    /// <summary>
    /// Removes the XML style.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>The value without XML style.</returns>
    private static string RemoveXmlStyle(string value)
    {
      const string DoubleSpace = "  ";
      const string Space = " ";
      if (value.StartsWith(Environment.NewLine))
      {
        value = value.Substring(Environment.NewLine.Length);
      }

      while (value.Contains(DoubleSpace) == true)
      {
        value = value.Replace(DoubleSpace, Space);
      }

      while (value.Contains(Environment.NewLine + Space) == true)
      {
        value = value.Replace(Environment.NewLine + Space, Environment.NewLine);
      }

      value = value.Trim();

      return value;
    }

    /// <summary>
    /// Creates the index file of the elements.
    /// </summary>
    /// <param name="typeCollection">The collection containing the types of the index.</param>
    /// <param name="docPath">The path where the documentation will be stored.</param>
    /// <param name="elementType">The NAnt type of the element.</param>
    private static void CreateElementsIndex(List<Type> typeCollection, string docPath, string elementType)
    {
      if (typeCollection.Count > 0)
      {
        GoogleCodeWiki wiki = new GoogleCodeWiki();        
        wiki.AddSummary(elementType + " Reference");
        wiki.AddTableHeader(elementType);        
        
        foreach (Type t in typeCollection)
        {
          wiki.AddTableRow(t.Name);
        }

        wiki.WriteToFile(Path.Combine(docPath, "index.wiki"));
      }
    }
  }
}
